//    Copyright (C) Kherty.  All rights reserved.

#region

using System;
using System.ComponentModel;
using System.Diagnostics.Contracts;
using System.IO;
using System.IO.Packaging;
using System.Text;
using System.Windows.Threading;
using System.Xml;
using OpenLS.Core;
using OpenLS.Core.Serialization;
using OpenLS.Spreadsheet.Formats;
using OpenLS.Spreadsheet.Internals;
using OpenLS.Spreadsheet.Serialization;

#endregion

namespace OpenLS.Spreadsheet
{
    public interface IDispatcherProvider
    {
        Dispatcher Dispatcher { get; }
    }

    ///<summary>
    /// Represents a spreadsheet document.
    ///</summary>
    public class SpreadsheetDocument : OfficeDocument
    {
        private const string documentContentType =
            @"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml";

        internal const string documentRelationship =
            @"http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";

        private readonly Guid _guid = Guid.NewGuid();
        private SpreadsheetContext _context;
        public string _defaultName;
        private bool _isDirty;
        private Workbook _workbook;

        public SpreadsheetDocument(SpreadsheetContext spreadsheetContext)
        {
            if (spreadsheetContext == null)
                throw new ArgumentNullException("spreadsheetContext");

            Context = spreadsheetContext;
        }
        ///<summary>
        /// Save the office document to disk. If the FileName property has not been set, call SaveAs instead.
        ///</summary>
        ///<exception cref="InvalidOperationException"></exception>
        public override void Save()
        {
            if (FileName == null)
                throw new InvalidOperationException();
            if (_format != null)
            {
                _format.Save(this, FileName);
                return;
            }
            SaveAs(FileName);
        }

        public override string FileName
        {
            get
            {
                return base.FileName;
            }
            set
            {
                base.FileName = value;
                this._format = null;
            }
        }
        private SpreadsheetFormat _format;
        public void SetFormat (SpreadsheetFormat format)
        {
            this._format = format;
        }

        public Guid Guid
        {
            get { return _guid; }
        }

        public bool IsDirty
        {
            get { return _isDirty; }
            internal set { _isDirty = value; }
        }

        protected override string DefaultName
        {
            get
            {
                if (_defaultName != null)
                    return _defaultName;
                return StringTable.Book;
            }
        }

        public SpreadsheetContext Context
        {
            get { return _context; }
            private set
            {
                Contract.Requires(value != null);
                _context = value;
            }
        }

        public Workbook Workbook
        {
            get
            {
                if (_workbook == null)
                {
                    _workbook = Workbook.Create(this);
                }
                ;
                return _workbook;
            }
            set { _workbook = value; }
        }

        public static SpreadsheetDocument Open(Stream stream)
        {
            using (Package p = Package.Open(stream))
            {
                return Open(p);
            }
        }

        public static SpreadsheetDocument Open(Stream stream, SpreadsheetContext spreadsheetContext)
        {
            using (Package p = Package.Open(stream))
            {
                return Open(p, spreadsheetContext);
            }
        }

        ///<summary>
        /// Open an existing spreadsheet document.
        ///</summary>
        ///<param name="path">The path of the file to open</param>
        ///<returns></returns>
        ///<exception cref="FileNotFoundException"></exception>
        public static SpreadsheetDocument Open(string path)
        {
            if (!File.Exists(path))
                throw new FileNotFoundException(path);
            return Open(path, SpreadsheetContext.Default);
        }

        ///<summary>
        /// Open an existing spreadsheet document.
        ///</summary>
        ///<param name="path">The path of the file to open</param>
        ///<param name="spreadsheetContext">The spreadsheet context containing the add-ins</param>
        ///<returns></returns>
        ///<exception cref="ArgumentNullException"></exception>
        ///<exception cref="FileNotFoundException"></exception>
        public static SpreadsheetDocument Open(string path, SpreadsheetContext spreadsheetContext)
        {
            if (spreadsheetContext == null)
                throw new ArgumentNullException("spreadsheetContext");
            if (!File.Exists(path))
                throw new FileNotFoundException(path);

            return Open(path, spreadsheetContext, null);
        }

        public static SpreadsheetDocument Open(string path, SpreadsheetContext spreadsheetContext,
                                                 BackgroundWorker opContext)
        {
            Contract.Requires(spreadsheetContext != null);
            if (!File.Exists(path))
                throw new FileNotFoundException(path);
#if NET40
            string extension = Path.GetExtension(path).ToLower();
            if (extension != null)
                extension = extension.Replace(".", "");
            if (extension != "xlsx")
            {
                foreach (var v in FormatManager.DataFormats)
                {
                    if (Enumerable.Any(v.Extensions, e => e == extension))
                    {
                        return v.Open(path);
                    }
                }
            }
#endif
            FileAttributes attributes = File.GetAttributes(path);
            if ((attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
                using (Package p = Package.Open(path, FileMode.Open, FileAccess.Read))
                {
                    return Open(p, path, spreadsheetContext, opContext);
                }

            else

                using (Package p = Package.Open(path))
                {
                    return Open(p, path, spreadsheetContext, opContext);
                }
        }

        public static SpreadsheetDocument Open(Stream stream, FileMode packageMode)
        {
            using (Package p = Package.Open(stream, packageMode))
            {
                return Open(p);
            }
        }

        public static SpreadsheetDocument Open(string path, FileMode packageMode)
        {
            if (!File.Exists(path))
                throw new FileNotFoundException(path);
            using (Package p = Package.Open(path, packageMode))
            {
                return Open(p, path);
            }
        }

        public static SpreadsheetDocument Open(Stream stream, FileMode packageMode, FileAccess packageAccess)
        {
            using (Package p = Package.Open(stream, packageMode, packageAccess))
            {
                return Open(p);
            }
        }

        public static SpreadsheetDocument Open(string path, FileMode packageMode, FileAccess packageAccess)
        {
            if (!File.Exists(path))
                throw new FileNotFoundException(path);
            using (Package p = Package.Open(path, packageMode, packageAccess))
            {
                return Open(p, path);
            }
        }

        public static SpreadsheetDocument Open(string path, FileMode packageMode, FileAccess packageAccess,
                                               FileShare packageShare)
        {
            using (Package p = Package.Open(path, packageMode, packageAccess, packageShare))
            {
                return Open(p, path);
            }
        }

        public static SpreadsheetDocument Open(Package package)
        {
            return Open(package, SpreadsheetContext.Default);
        }

        public static SpreadsheetDocument Open(Package package, SpreadsheetContext spreadsheetContext)
        {
            if (spreadsheetContext == null)
                throw new ArgumentNullException("spreadsheetContext");

            var result = new SpreadsheetDocument(spreadsheetContext);
            result.ReadPackage(package);
            return result;
        }

        public static SpreadsheetDocument Open(Package package, string path)
        {
            return Open(package, path, SpreadsheetContext.Default);
        }

        internal static SpreadsheetDocument Open(Package package, string path, BackgroundWorker opContext)
        {
            return Open(package, path, SpreadsheetContext.Default, opContext);
        }

        public static SpreadsheetDocument Open(Package package, string path, SpreadsheetContext spreadsheetContext)
        {
            if (spreadsheetContext == null)
                throw new ArgumentNullException("spreadsheetContext");

            var result = new SpreadsheetDocument(spreadsheetContext);
            result.FileName = path;
            result.ReadPackage(package);
            return result;
        }

        public static SpreadsheetDocument Open(Package package, string path, SpreadsheetContext spreadsheetContext,
                                                 BackgroundWorker opContext)
        {
            Contract.Requires(spreadsheetContext != null);
            var result = new SpreadsheetDocument(spreadsheetContext) {FileName = path};
            result.ReadPackage(package, opContext);
            return result;
        }

        public void SetDirty()
        {
            if (IsDirty)
                return;
            IsDirty = true;
        }

        public static SpreadsheetDocument Create()
        {
            return Create(SpreadsheetContext.Default);
        }

        public static SpreadsheetDocument Create(SpreadsheetContext spreadsheetContext)
        {
            if (spreadsheetContext == null)
                throw new ArgumentNullException("spreadsheetContext");

            var result = new SpreadsheetDocument(spreadsheetContext);
            result.Workbook = Workbook.Create(result);
            result.Context = spreadsheetContext;
            return result;
        }

        public static SpreadsheetDocument CreateEmpty(SpreadsheetContext spreadsheetContext)
        {
            var result = new SpreadsheetDocument(spreadsheetContext);
            result.Workbook = Workbook.CreateEmpty(result);
            result.Context = spreadsheetContext;
            return result;
        }

        public static SpreadsheetDocument Create(string fileName)
        {
            return Create(fileName, SpreadsheetContext.Default);
        }

        public static SpreadsheetDocument Create(string fileName, SpreadsheetContext spreadsheetContext)
        {
            if (spreadsheetContext == null)
                throw new ArgumentNullException("spreadsheetContext");

            var result = new SpreadsheetDocument(spreadsheetContext);
            result.Workbook = Workbook.Create(result);
            result.FileName = fileName;
            result.Context = spreadsheetContext;
            return result;
        }

        public override void ReadPackage(Package package, BackgroundWorker opContext)
        {
            ReadCoreProperties(package);
            ReadExtendedProperties(package);
            PackageRelationship rel = null;
            foreach (PackageRelationship relationship in package.GetRelationships())
            {
                if (relationship.RelationshipType == documentRelationship)
                    rel = relationship;
            }
            PackagePart documentPart = package.GetPart(PackUriHelper.ResolvePartUri(rel.SourceUri, rel.TargetUri));
            //\\this.FileName = FileName;
            Workbook = new Workbook(this);
            using (Stream stream = documentPart.GetStream())
            using (XmlReader xmlReader = new XmlTextReader(stream))
            {
                Workbook.UndoEnabled = false;
                var rc = new ReadContext(xmlReader, documentPart, SpreadsheetConstants.SpreadsheetNamespaceUri);
                var provider = opContext as IDispatcherProvider;
                if (provider != null)
                    rc.AddData(provider.Dispatcher);
                Workbook.AddToReadContext(rc);
                rc.AddData(opContext);
                rc.AddData(Context);
                Workbook.ReadXml(rc);
                Workbook.UndoEnabled = true;
            }
            SetNotDirty();
        }

        public override void WritePackage(Package package)
        {
            WriteCoreProperties(package);
            WriteExtendedProperties(package);
            var workbookUri = new Uri("/xl/workbook.xml", UriKind.Relative);
            PackageRelationship r = package.CreateRelationship(workbookUri, TargetMode.Internal, documentRelationship);
            Uri uu = PackUriHelper.ResolvePartUri(r.SourceUri, r.TargetUri);
            PackagePart workbookPart = package.CreatePart(uu, documentContentType, CompressionOption.Maximum);
            using (Stream stream = workbookPart.GetStream())
            using (XmlWriter xmlWriter = new XmlTextWriter(stream, Encoding.UTF8))
            {
                var ctx = new WriteContext(SpreadsheetConstants.Spreadsheet, xmlWriter, workbookPart);
                ctx.SetData(typeof (Workbook), Workbook);
                ctx.WriteElement(Workbook);
            }
        }

        protected override void SetNotDirty()
        {
            IsDirty = false;
        }

        public static SpreadsheetDocument OpenEmpty(string fileName, SpreadsheetContext spreadsheetContext)
        {
            if (spreadsheetContext == null)
                throw new ArgumentNullException("spreadsheetContext");
            var doc = new SpreadsheetDocument(spreadsheetContext);
            doc.FileName = fileName;
            return doc;
        }
    }
}